﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Hec.TextMining
{
    public class Cluster
    {
        public int ClusterID { get; set; }
        public IList<Hec.TextMining.KeyValuePair<ClusteredDocument, List<Hec.TextMining.KeyValuePair<string, Cell>>>> Documents;
        public IList<Hec.TextMining.KeyValuePair<string, double>> Centroid;

        public Cluster()
        {
            Documents = new List<Hec.TextMining.KeyValuePair<ClusteredDocument, List<Hec.TextMining.KeyValuePair<string, Cell>>>>();
            Centroid = new List<Hec.TextMining.KeyValuePair<string, double>>();
        }
    }

    public class ClusteredDocument : Document
    {
        public Hec.TextMining.KeyValuePair<int, double> DistanceToCentroid { get; set; }
        public Hec.TextMining.KeyValuePair<int, double> BestDistanceToCentroid { get; set; }
        public ClusteredDocument(Document document)
        {
            //todo : fix this
            this.Distances = document.Distances;
            this.FullName = document.FullName;
            this.ID = document.ID;
            this.TotalTermCount = document.TotalTermCount;
        }
    }

    public class Clustering
    {
        public int NumberOfClusters { get; set; }
        public IList<Cluster> Clusters;
        IList<Hec.TextMining.KeyValuePair<Document, List<Hec.TextMining.KeyValuePair<string, Cell>>>> Dissimilarities;


        public Clustering(int numberOfClusters, IList<Hec.TextMining.KeyValuePair<Document, List<Hec.TextMining.KeyValuePair<string, Cell>>>> dissimilarities)
        {
            NumberOfClusters = numberOfClusters;
            Clusters = new List<Cluster>(NumberOfClusters);
            for (int i = 0; i < NumberOfClusters; i++)
            {
                Clusters.Add(new Cluster() { ClusterID = i + 1 });
            }
            Dissimilarities = dissimilarities;
        }

        public void _1_Initialization(int? randomSeed)
        {
            //attribuer un cluster # random pour chaque sujet
            // do in separate method so you couls provide strategy for random
            var random = new Random();
            if (randomSeed.HasValue)
            {
                random = new Random(randomSeed.Value);
            }
            else
            {
                random = new Random(DateTime.Now.Millisecond);
            }

            Dissimilarities
                .Select(x =>
                {
                    var result = new Hec.TextMining.KeyValuePair<ClusteredDocument, List<Hec.TextMining.KeyValuePair<string, Cell>>>(new ClusteredDocument(x.Key), x.Value);
                    return new { KVP = result, ClusterNumber = random.Next(1, NumberOfClusters + 1) };
                })
                .Select(x =>
                {
                    Clusters.Single(y => y.ClusterID == x.ClusterNumber).Documents.Add(x.KVP);
                    return x;
                })
                .ToList();
        }

        public void _2_CalculateCentroidOfClusters()
        {
            //c'est la moyenne des distances pour chaque terme
            Clusters.Select(cluster =>
            {
                var results =
                                cluster
                                    .Documents
                                    .SelectMany(x => x.Value)
                                    .GroupBy(x => x.Key)
                                    .Select(group =>
                                    {
                                        //moyenne pour terme_i
                                        var mean = group.Sum(x => x.Value.TF_IDF) / (double)(group.Count());
                                        return new Hec.TextMining.KeyValuePair<string, double>(group.Key, mean);
                                    })
                                    .ToList();
                cluster.Centroid = results;
                return cluster;
            })
            .ToList();

            //todo : gérer l'exception dans le cas ou le random a mis 0 observations dans un ou des clustesrs.  
        }

        public void _3_K_Means()
        {
            var movedDocuments = 0;

            do
            {
                movedDocuments = 0;
                //pour chaque doc, calculer la distance moyenne au centroide auquel il appartient 
                Clusters
                    .Select(cluster =>
                    {
                        // todo : affect correctly calculated clustereddocument
                        var clusteredDocuments = cluster
                                                    .Documents
                                                    .AsParallel()
                                                    .Select(document =>
                                                    {
                                                        var documentDiffSq = document
                                                                                .Value
                                                                                .Select(term =>
                                                                                {
                                                                                    var difference = (cluster.Centroid.Single(x => x.Key == term.Key).Value - term.Value.TF_IDF);
                                                                                    return Math.Pow(difference, 2);
                                                                                })
                                                                                .Sum(x => x);
                                                        var result = new ClusteredDocument(document.Key)
                                                                        {
                                                                            DistanceToCentroid = new KeyValuePair<int, double>(cluster.ClusterID, documentDiffSq)
                                                                        };
                                                        document.Key = result;
                                                        return document;
                                                    })
                                                    .ToList();
                        cluster.Documents = clusteredDocuments;
                        return cluster;
                    }).ToList();


                //Pour chaque document, calculer la distance aux autres clusters autres que le sien et conserver la valeur minimale. 
                var counter = 0;
                Clusters.SelectMany(cluster => cluster.Documents)
                        .AsParallel()
                        .Select(document =>
                        {
                            if (counter < 10)
                            {
                                Clusters
                                    .Where(x => x.ClusterID != document.Key.DistanceToCentroid.Key)
                                    .Select(otherCluster =>
                                    {
                                        var diffWithOtherCluster = document
                                                                    .Value
                                                                    .Select(term =>
                                                                    {
                                                                        var difference = (otherCluster.Centroid.Single(x => x.Key == term.Key).Value - term.Value.TF_IDF);
                                                                        return Math.Pow(difference, 2);
                                                                    })
                                                                    .Sum(x => x);

                                        if (diffWithOtherCluster < document.Key.DistanceToCentroid.Value)
                                        {
                                            document.Key.BestDistanceToCentroid = new KeyValuePair<int, double>(otherCluster.ClusterID, diffWithOtherCluster);
                                            counter++;
                                        }

                                        return otherCluster;
                                    })
                                    .ToList();

                            }
                            return document;
                        })
                        .ToList();


                Clusters.Select(cluster =>
                        {
                            var documentsToMove = cluster.Documents
                                                    .Where(x => x.Key.DistanceToCentroid.Key == cluster.ClusterID)
                                                    .Where(x => x.Key.BestDistanceToCentroid.Value != 0)
                                                    .ToList();
                            documentsToMove.ForEach(x => cluster.Documents.Remove(x));


                            documentsToMove.ForEach(x =>
                            {
                                movedDocuments++;
                                x.Key.DistanceToCentroid = x.Key.BestDistanceToCentroid;
                                x.Key.BestDistanceToCentroid = new KeyValuePair<int, double>(0, 0);


                                Clusters
                                    .Single(y => y.ClusterID == x.Key.DistanceToCentroid.Key)
                                    .Documents
                                    .Add(x);

                            });

                            return cluster;
                        })
                        .ToList();
                
                if (movedDocuments > 0)
                {
                    this._2_CalculateCentroidOfClusters();
                }

            }
            while (movedDocuments > 0);
        }

    }
}
